/**
 * @author: talent.tan
 * https://gitee.com/tywo45
 * https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
 */
import * as tio from "@/util/tio";
import * as c from "@/util/c";

import { log } from "@/util/tio";
import { timeStamp } from "console";
import { format } from 'fecha';


import qs from "query-string";

export default class TioWebsocket {
    metaProvider: Function;  //返回值参考：{ ip, port, ssl, timeout, params }, 
    #pingInterval = 3000     //发送心跳的间隔，毫秒
    meta: { ip, port, ssl, timeout, params }  //{ ip, port, ssl, timeout, params }
    ping: Function;     //发心跳的函数，可以为null，如果为null，则不主动发心跳
    ws: WebSocket;
    options = { reconnInterval: 5000, binaryType: 'arraybuffer', ping: null };
    destroyed = false;
    status = 'off' //off / on
    onopen: Function     // this.onopen(ws, isReconn, event);
    onmessage: Function
    onclose: Function
    onerror: Function
    ondestroy: Function
    #intervalId: any;    //心跳任务定时器的intervalId
    #recount = 0    //重连次数
    #inuseing = false  //是否正在使用，如果已经在使用中，则不允许外部再调connect()
    #lastInteract: any;  //上一次的消息交互时间
    constructor(metaProvider: Function, options: any) {
        Object.assign(this.options, options);
        let { ping, binaryType } = options;
        this.metaProvider = metaProvider;
        this.ping = ping;
        // this.#recount = 0;
    }
    #updateLastInteract = () => {  //更新上次的交互时间
        this.#lastInteract = Date.now()
    }
    #calcPingInterval(timeout) {
        if (timeout) {
            if (timeout >= 3000) {
                this.#pingInterval = timeout - 2000
            } else {
                this.#pingInterval = timeout / 2
            }
        }
    }
    async connect() {
        if (this.#inuseing) {
            return
        }
        return this.#_connect(false);
    }
    async #_connect(isReconn: Boolean = false) {
        this.#inuseing = true
        this.destroyed = false
        this.meta = await this.metaProvider(isReconn);
        this.#recount++;
        if (!this.meta || !this.meta.ip) {
            setTimeout(() => {
                this.#_connect(isReconn)
            }, this.options.reconnInterval)
            return
        }
        let { ip, port, ssl, timeout, params } = this.meta;
        this.#calcPingInterval(timeout)
        let queryStr = qs.stringify(params)
        let wsurl = `${ssl == 1 ? "wss" : "ws"}://${ip}:${port}/?tio_recount=${this.#recount}&tio_reconn=${isReconn}&${queryStr}`;
        this.ws = new WebSocket(wsurl);
        let ws = this.ws;
        ws.binaryType = this.options.binaryType
        ws.onopen = (event) => {
            this.status = 'on'
            this.#updateLastInteract();
            this.#intervalId = setInterval(() => {
                let date = new Date();
                let iv = date.getTime() - this.#lastInteract
                if (this.meta.timeout - 1000 <= this.#pingInterval + iv) {
                    log(`${format(date, c.DATE_FORMAT_DATETIME)} send ping, ${iv}, ${this.#pingInterval}`)
                    this.ping(this, ws);
                } else {
                    log(`${format(date, c.DATE_FORMAT_DATETIME)} not need send ping, ${iv}, ${this.#pingInterval}`)
                }
            }, this.#pingInterval);

            if (this.onopen) {
                this.onopen(this, ws, isReconn, event);
            }
        };
        ws.onmessage = (event) => {
            this.#updateLastInteract();
            this.onmessage(this, ws, event.data, event);
        };
        ws.onclose = (event) => {
            log('ws----onclose');
            this.status = 'off'
            this.#stopTimer()
            if (this.onclose) {
                try {
                    this.onclose(this, ws, event);
                } catch (error) { }
            }
            this.#reconn(event);
        };
        ws.onerror = (event) => {
            log('ws----onerror');
            if (this.onerror) {
                this.onerror(this, ws, event);
            }
        };
    }
    destroy() {
        this.destroyed = true
        this.#inuseing = false
        this.#recount = 0
        this.ws.close()
        this.ondestroy(this)
        this.#stopTimer()
    }
    disconnect() {
        this.ws.close()
        this.#stopTimer()
    }
    #stopTimer() {
        if (this.#intervalId) {
            clearInterval(this.#intervalId)
            this.#intervalId = null
        }
    }
    #reconn(event) {
        if (!this.destroyed) {
            setTimeout(async () => {
                if (!this.destroyed) {
                    this.#_connect(true);
                }
            }, this.options.reconnInterval);
        }
    }
    send(data) {
        this.#updateLastInteract();
        this.ws.send(data);
    }
    state() {
        return {
            destroyed: this.destroyed,
            status: this.status,
            meta: this.meta
        }
    }
}
/**
 * 使用示范
 * show how to use
 */
function demo() {
    let metaProvider = () => {
        return {
            "timeout": "15000",
            "ip": "www.tiocloud.com",
            "port": 9325,
            "ssl": 1,
            "params": { name: 'talent.tan', id: '666666' }
        }
    }
    let ping = (tws, ws) => {
        ws.send('hello t-io more info: https://gitee.com/tywo45');
    }
    let tws = new TioWebsocket(metaProvider, { ping, binaryType: 'arraybuffer', reconnInterval: 5000 })
    tws.onopen = (tws, ws, isReconn, event) => {
        tws.send('hello t-io');
    }
    tws.onmessage = (tws, ws, data, event) => {

    }
    tws.onclose = (tws, ws, event) => {

    }
    tws.onerror = (tws, ws, event) => {

    }
    tws.ondestroy = (tws) => {
    }
    tws.connect();
}